# encoding: utf-8
# Copyright 2012, Google Inc. All Rights Reserved.
"""
Cache
"""
__author__ = 'davidthorpe@google.com'

from google.appengine.ext import db
from google.appengine.api import memcache
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from django.utils import simplejson as json

import constants

import os
import logging
logger = logging.getLogger('root')

class FileNotFound(Exception):
	pass

class BlockVariant(db.Model):
	name = db.StringProperty(indexed=True)
	value = db.IntegerProperty()
	tuples = db.TextProperty()

	@staticmethod
	def _template_path(name):
		path = os.path.join(constants.BLOCK_PATH,"%s.html" % name)
		logger.debug("_template_path %s => %s" % (name,path))
		if not os.path.isfile(path):
			logger.error("Template not found: %s" % path)
			raise FileNotFound("Resource not found: %s" % name)
		return path

	@staticmethod
	def is_valid_name(name):
		try:
			BlockVariant._template_path(name)
			return True
		except FileNotFound, e:
			return False

	def _get_tuples(self):
		all_tuples = None
		if self.tuples:
			all_tuples = json.loads(self.tuples)
		assert all_tuples==None or isinstance(all_tuples,dict),"invalid tuples"
		if all_tuples==None:
			all_tuples = { }
		return all_tuples

	def set_tuple(self,name,value=None):
		assert isinstance(name,unicode) and len(name),"invalid name parameter"
		assert value==None or isinstance(value,unicode),"invalid value parameter"
		all_tuples = self._get_tuples()
		if value:
			all_tuples[name] = value
		elif name in all_tuples:
			del all_tuples[name]
		self.tuples = json.dumps(all_tuples)
		
	def get_content(self):
		template_path = self._template_path(self.name)
		tuples = self._get_tuples()
		logger.debug("get_content: %s: %s" % (template_path,tuples))
		return webapp.template.render(template_path,tuples)

class State(db.Model):
	name = db.StringProperty(indexed=True)
	value = db.IntegerProperty()
	counter = db.IntegerProperty()

class StoreCache:
	def __init__(self):
		self._statecache = StateCache()
		self._max = constants.BLOCK_MAX
	@property
	def state(self):
		return self._statecache

	def get_block_variant(self,name,variant):
		assert isinstance(variant,long) and variant > 0,"invalid variant parameter"
		assert isinstance(name,unicode) and len(name),"invalid name parameter"
		query = BlockVariant.all()
		query.filter("name =","%s:%d" % (name,variant))
		entities = query.fetch(1)
		for entity in entities:
			return entity
		return None

	def new_block_variant(self,name,variant):
		assert isinstance(variant,long) and variant > 0,"invalid variant parameter"
		assert isinstance(name,unicode) and len(name),"invalid name parameter"
		entity = self.get_block_variant(name,variant)
		if entity:
			return entity
		# check for valid entity name
		if not BlockVariant.is_valid_name(name):
			logging.debug("new_block_variant: %s is not a valid name" % name)
			return None
		# create entity
		logging.debug("new_block_variant: creating new variant for name=%s variant=%s" % (name,variant))
		entity = BlockVariant()
		entity.name = name
		entity.value = variant
		entity.put()
		# TODO: set state
		return entity

	def save_block_variant(self,entity):
		assert isinstance(entity,BlockVariant),"invalid entity parameter"
		# store the block
		entity.put()
		# if state does not exist for this block name, then we set state
		if self._statecache.get_state_entity(entity.name)==None:
			logging.debug("Resetting state for: %s" % entity.name)
			self._statecache.reset_state(entity.name,entity.value)
		# else we increment the counter
		else:
			self._statecache.increment_state_counter(entity.name)
		return entity
		
	def reset_all(self):
		logging.debug("StoreCache: reset_all")
		entities = BlockVariant.all().fetch(self._max)
		for entity in entities:
			entity.delete()
		self.state.reset_all()
		return True

class StateCache:
	def __init__(self):
		self._key = constants.STATE_MEMCACHEKEY
		self._interval = constants.STATE_DEFAULTINTERVAL
		self._max = constants.BLOCK_MAX
	def reset_all(self):
		logging.debug("StateCache: reset_all")
		entities = State.all().fetch(self._max)
		for entity in entities:
			entity.delete()
		self.reset_state_json()
	def reset_state_json(self):
		logging.debug("StateCache: called reset_state_json")
		if not memcache.delete(self._key):
			logging.error("StateCache: Unable to add memcache for key: %s" % self._key)
			raise Exception("StateCache: Unable to delete memcache for key: %s" % self._key)
	def reset_state(self,name,value=None):
		assert (name and isinstance(name,unicode) and len(name)),"invalid name parameter"
		assert value==None or (value and isinstance(value,long) and value > 0),"invalid value parameter"
		# get entity or create a new one
		entity = self.get_state_entity(name)
		if entity==None:
			entity = State()
		# reset values
		entity.name = name
		if value:
			entity.value = value
		else:
			entity.value = 1
		entity.counter = 1
		entity.put()
		logging.debug("<Block %s> value=%d counter=%d" % (name,entity.value,entity.counter))
		# clear json
		self.reset_state_json()
		# return current value
		return long(entity.value)
	def set_state_json(self,values):
		assert (values and isinstance(values,dict) and len(values)),"invalid values parameter"
		logging.debug("StateCache: set_state_json")
		if not memcache.add(self._key,json.dumps(values)):
			logging.error("StateCache: Unable to add memcache for key: %s" % self._key)
			raise Exception("StateCache: Unable to add memcache for key: %s" % self._key)
	def get_state_json(self):
		# try and obtain JSON from the memcache
		values = memcache.get(self._key)
		if values and isinstance(values,str) and len(values):
			return values
		logging.debug("StateCache: Cannot fetch state from memcache, retrieving from storage")
		values = self.get_state_counters()
		if 'interval' not in values:
			logging.debug("StateCache: Setting default interval")
			values['interval'] = constants.STATE_DEFAULTINTERVAL
		if values:
			# store values in memcache
			self.set_state_json(values)
		return json.dumps(values)
	def get_state_entity(self,name):
		assert (name and isinstance(name,unicode) and len(name)),"invalid name parameter"
		logging.debug("StateCache: get_state_entity: %s" % name)
		query = State.all()
		query.filter("name =",name)
		entities = query.fetch(1)
		for entity in entities:
			return entity
		return None
	def get_state_counters(self):
		logging.debug("StateCache: get_state_values")
		entities = State.all().fetch(self._max)
		values = { }
		for entity in entities:
			values[entity.name] = entity.counter
		return values
	def get_state_value(self,name):
		assert (name and isinstance(name,unicode) and len(name)),"invalid name parameter"
		entity = self.get_state_entity(name)
		if entity==None:
			logging.error("StateCache: get_state_value: unknown name: %s" % name)
			return None
		return long(entity.value)
	def set_state_value(self,name,value):
		assert (name and isinstance(name,unicode) and len(name)),"invalid name parameter"
		assert (value and isinstance(value,long) and value > 0),"invalid value parameter"
		entity = self.get_state_entity(name)
		if entity==None:
			logging.error("StateCache: set_state_value: unknown name: %s" % name)
			return None
		entity.value = value
		entity.counter = entity.counter + 1
		logging.debug("<Block %s> value=%d counter=%d" % (name,entity.value,entity.counter))
		entity.put()
		self.reset_state_json()
		return long(value)
	def increment_state_counter(self,name):
		assert (name and isinstance(name,unicode) and len(name)),"invalid name parameter"
		entity = self.get_state_entity(name)
		if entity==None:
			logging.error("StateCache: increment_state_counter: unknown name: %s" % name)
			return None
		entity.counter = entity.counter + 1
		logging.debug("<Block %s> value=%d counter=%d" % (name,entity.value,entity.counter))
		entity.put()
		self.reset_state_json()
		return long(entity.value)
